Skip to content

Let the module resolve a backend session, so proxied Composio can run in it - #102

Merged
YellowSnnowmann merged 1 commit into
tinyhumansai:mainfrom
YellowSnnowmann:feat/5560-backend-session-seam
Aug 25, 2026
Merged

Let the module resolve a backend session, so proxied Composio can run in it#102
YellowSnnowmann merged 1 commit into
tinyhumansai:mainfrom
YellowSnnowmann:feat/5560-backend-session-seam

Conversation

@YellowSnnowmann

Copy link
Copy Markdown
Contributor

Unblocks the last of openhuman#5560. Without this the host cannot delete its in-process engine without breaking Composio sync for its default configuration.

The problem

Composio sync has two credential paths and only one worked inside a loaded module:

if mode == "direct" {
    let api_key = composio_host::api_key(config)   // a SEAM — works in the module
} else {
    let bearer = config.session_token()?           // a CONFIG FIELD — does not
}

Inside a module, Config is EngineRuntimeConfig — a load-time snapshot with no bearer in it — so session_token refuses by design and every proxied user falls out of the branch.

The consequence was quiet and large: composio_sync_can_run gated the periodic loop on direct mode, so a host whose Composio mode is backend — OpenHuman's default — got a loop that never started. Neither side reported it, because neither believed it was responsible.

The fix

ComposioHost gains session_bearer, sitting beside the api_key that already works. composio_config's proxied branch consults the seam first and falls back to the config exactly as before, so a host running the engine in-process is unaffected — no seam installed there, the accessor answers None, the old config read still happens.

Why a seam and not a ModuleConfig field

The bearer is an app-session JWT the host refreshes. A value captured at module load works until it expires and then makes every sync fail with an auth error that reads as the user being signed out — the silent-staleness failure this migration keeps having to design against. Asking per call means the answer is always the one valid now. Same reasoning api_key already carries.

What the gate still excludes

Both modes qualify now: direct resolves a key, backend resolves a bearer. Still refused is a host that resolved to neither — an empty or unrecognised mode string has no credential path, so starting the loop would fail every tick and append a failed audit row each time.

Two deliberate asymmetries

The trait member is defaulted to None, so a host predating it compiles unchanged and falls back to the config read.

session_bearer does not copy is_available's optimism. That probe answers true when it cannot reach the host, because a wrong false reads as "not signed in" and hides a broken sync. A credential is not something to be optimistic about: an unreachable host yields None, which lets composio_config refuse by name rather than send an empty bearer at the backend. The test pins both halves.

Not a registered bus method

ComposioHost is served by the host and consumed by the module, like ChatHost, EmbeddingHost and RuntimeHost — so it appears in none of METHODS, the module manifest or EXPECTED_METHODS, and the drift assertion is untouched.

Validation

cargo check --workspace --all-targets clean in both workspaces; cargo test --workspace 2034 passing, module lane 70 passing; clippy -D warnings clean on root and module; cargo fmt clean on both; cargo doc --no-deps --all-features clean under -D warnings.

One existing test changed meaning rather than being deleted: composio_periodic_sync_starts_only_when_the_host_resolved_direct_mode asserted the constraint this PR removes. It is now ..._starts_for_any_mode_that_can_resolve_a_credential, with the reason for the change written into its doc.

… in it

Composio sync has two credential paths and only one of them worked inside a
loaded module. The direct branch reads its API key through
`ComposioHost::api_key`, a seam the host answers per call. The proxied branch
read `Config::session_token`, which inside a module is a load-time snapshot
carrying no bearer — so `EngineRuntimeConfig` refused it by design and every
proxied user fell out of the branch.

The consequence was quiet and large. A host whose Composio mode is backend —
which is OpenHuman's default — got a periodic sync loop that never started,
because `composio_sync_can_run` gated on direct mode, and neither the host nor
the module reported it, since neither believed it was responsible.

`ComposioHost` gains `session_bearer`, beside the `api_key` that already works.
`composio_config`'s proxied branch consults the seam first and falls back to the
config exactly as before, so a host running the engine in-process behaves
identically — no seam is installed there, the accessor answers `None`, and the
old config read still happens.

## Why a seam rather than a field on ModuleConfig

The bearer is an app-session JWT the host refreshes. A value captured at module
load works until it expires and then makes every sync fail with an auth error
that reads as the user being signed out — the silent-staleness failure this
whole migration keeps having to design against. Asking per call means the answer
is always the one that is valid now. It is the same reasoning `api_key` already
carries, and the reason that member is fetched per call too.

## What the gate now excludes

Both modes qualify: direct resolves a key, backend resolves a bearer. What is
still refused is a host that resolved to *neither* — an empty or unrecognised
mode string, which has no credential path at all, so starting the loop would
fail on every tick and append a failed audit row each time.

The trait member is defaulted to `None`, so a host that predates it compiles
unchanged and falls back to the config read it always did.

`session_bearer` deliberately does NOT copy `is_available`'s optimism. That
probe answers `true` when it cannot reach the host, because a wrong `false`
there reads as "not signed in" and hides a broken sync. A credential is not
something to be optimistic about: an unreachable host yields `None`, which lets
`composio_config` refuse by name rather than send an empty bearer at the
backend.
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fe859006-dd05-420f-91b4-9f4e9be37def

Warning

Your free Security trial is over. An organization admin can activate billing to continue.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

$0.0000 · 0 in / 0 out

@tinysweeper

tinysweeper Bot commented Aug 25, 2026

Copy link
Copy Markdown

How this change flows

1 changed behaviour across 5 relationships. 3 surrounding behaviours are shown (60 graph nodes walked). 50 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["composio_config<br/>changed"]:::changed
  n1["run_composio_connection_with_caps"]:::impacted
  n2["Result"]:::impacted
  n3["run_gmail_backfill"]:::impacted
  n0 -->|uses| n2
  n1 -->|calls| n0
  n1 -->|uses| n2
  n3 -->|calls| n0
  n3 -->|uses| n2
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 25, 2026
@YellowSnnowmann
YellowSnnowmann merged commit 90e58ad into tinyhumansai:main Aug 25, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant